package it.trekker.activities;

import android.app.ListActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import org.teleal.cling.android.AndroidUpnpService;
import org.teleal.cling.controlpoint.ActionCallback;
import org.teleal.cling.model.action.ActionArgumentValue;
import org.teleal.cling.model.action.ActionInvocation;
import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Device;
import org.teleal.cling.model.meta.DeviceIdentity;
import org.teleal.cling.model.meta.LocalDevice;
import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.types.InvalidValueException;
import org.teleal.cling.model.types.UDAServiceId;
import org.teleal.cling.registry.DefaultRegistryListener;
import org.teleal.cling.registry.Registry;
import org.teleal.cling.transport.SwitchableRouter;
import com.google.gson.Gson;
import it.trekker.dataexchange.PoiRealTimeInfo;
import it.trekker.dataexchange.service.UPnPDataExchangeService;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;


public class UPnPActivity extends ListActivity {

	private static final String TAG = UPnPActivity.class.getSimpleName(); 
	
	//	private static final Logger log = Logger.getLogger(UPnPActivity.class.getName());

	private ArrayAdapter<DeviceDisplay> listAdapter;
	private List<DeviceIdentity> deviceIdentityList = new ArrayList<DeviceIdentity>();	// TODO
	private BrowseRegistryListener registryListener = new BrowseRegistryListener();
	private AndroidUpnpService upnpService;


	private ServiceConnection serviceConnection = new ServiceConnection() {

		@Override
		public void onServiceConnected(ComponentName className, IBinder service) {
			upnpService = (AndroidUpnpService) service;

			// Refresh the list with all known devices
			listAdapter.clear();
			for (Device device : upnpService.getRegistry().getDevices()) {
				registryListener.deviceAdded(device);
			}

			// Getting ready for future device advertisements
			upnpService.getRegistry().addListener(registryListener);

			// Search asynchronously for all devices
			upnpService.getControlPoint().search();
		}

		@Override
		public void onServiceDisconnected(ComponentName className) {
			upnpService = null;
		}
	};


	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		listAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1);
		setListAdapter(listAdapter);
		
		getListView().setOnItemClickListener(new OnItemClickListener());

		getApplicationContext().bindService(
				new Intent(this, UPnPDataExchangeService.class),
				serviceConnection,
				Context.BIND_AUTO_CREATE
				);
	}


	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (upnpService != null) {
			upnpService.getRegistry().removeListener(registryListener);
		}
		getApplicationContext().unbindService(serviceConnection);
	}
	
	
	
	
	
	
	//*************************************************************************//
	//****************************** Option menu ******************************//
	//*************************************************************************//

	
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		menu.add(0, 0, 0, "Search lan").setIcon(android.R.drawable.ic_menu_search);
		menu.add(0, 1, 0, "Switch router").setIcon(android.R.drawable.ic_menu_revert);
		menu.add(0, 2, 0, "Toggle debug logging").setIcon(android.R.drawable.ic_menu_info_details);
		return true;
	}


	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		
			case 0:
				searchNetwork();
				break;
				
			case 1:
				if (upnpService != null) {
					SwitchableRouter router = (SwitchableRouter) upnpService.get().getRouter();
					if (router.isEnabled()) {
						Toast.makeText(this, "Disabling router", Toast.LENGTH_SHORT).show();
						router.disable();
					} else {
						Toast.makeText(this, "Enabling router", Toast.LENGTH_SHORT).show();
						router.enable();
					}
				}
				break;
				
			case 2:
				Logger logger = Logger.getLogger("org.teleal.cling");
				if (logger.getLevel().equals(Level.FINEST)) {
					Toast.makeText(this, "Disabling debug logging", Toast.LENGTH_SHORT).show();
					logger.setLevel(Level.INFO);
				} else {
					Toast.makeText(this, "Enabling debug logging", Toast.LENGTH_SHORT).show();
					logger.setLevel(Level.FINEST);
				}
				break;
				
		}
		return false;
	}





	
	//**************************************************************************//
	//***************************** Helper classes *****************************//
	//**************************************************************************//
	

	protected class BrowseRegistryListener extends DefaultRegistryListener {

		/* Discovery performance optimization for very slow Android devices! */

		@Override
		public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device) {
			deviceAdded(device);
		}

		@Override
		public void remoteDeviceDiscoveryFailed(Registry registry, final RemoteDevice device, final Exception ex) {
			showToast(
					"Discovery failed of '" + device.getDisplayString() + "': " +
							(ex != null ? ex.toString() : "Couldn't retrieve device/service descriptors"),
							true
					);
			deviceRemoved(device);
		}
		/* End of optimization, you can remove the whole block if your Android handset is fast (>= 600 Mhz) */

		@Override
		public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
			deviceAdded(device);
		}

		@Override
		public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
			deviceRemoved(device);
		}

		@Override
		public void localDeviceAdded(Registry registry, LocalDevice device) {
			deviceAdded(device);
		}

		@Override
		public void localDeviceRemoved(Registry registry, LocalDevice device) {
			deviceRemoved(device);
		}

		public void deviceAdded(final Device device) {
			runOnUiThread(new Runnable() {
				@Override
				public void run() {
					DeviceDisplay d = new DeviceDisplay(device);

					int position = listAdapter.getPosition(d);
					if (position >= 0) {
						// Device already in the list, re-set new value at same position
						listAdapter.remove(d);
						listAdapter.insert(d, position);
					} else {
						listAdapter.add(d);
						deviceIdentityList.add(d.getDevice().getIdentity());	// TODO
					}

					// Sort it?
					// listAdapter.sort(DISPLAY_COMPARATOR);
					// listAdapter.notifyDataSetChanged();
				}
			});
		}

		public void deviceRemoved(final Device device) {
			runOnUiThread(new Runnable() {
				@Override
				public void run() {
					listAdapter.remove(new DeviceDisplay(device));
				}
			});
		}
	}



	protected class DeviceDisplay {

		Device device;

		public DeviceDisplay(Device device) {
			this.device = device;
		}

		public Device getDevice() {
			return device;
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (o == null || getClass() != o.getClass()) return false;
			DeviceDisplay that = (DeviceDisplay) o;
			return device.equals(that.device);
		}

		@Override
		public int hashCode() {
			return device.hashCode();
		}

		@Override
		public String toString() {
			String name =
					device.getDetails() != null && device.getDetails().getFriendlyName() != null
					? device.getDetails().getFriendlyName()
							: device.getDisplayString();
					// Display a little star while the device is being loaded (see performance optimization earlier)
					return device.isFullyHydrated() ? name : name + " *";
		}
	}
	
	
	protected class GetDataActionInvocation extends ActionInvocation {

		GetDataActionInvocation(Service service) {
			super(service.getAction("RetrieveData"));
			try {

				// Throws InvalidValueException if the value is of wrong type
//				setInput("NewTargetValue", true);

			} catch (InvalidValueException ex) {
				Log.e(TAG, ex.getMessage());
			}
		}
	}
	
	
	protected class OnItemClickListener implements AdapterView.OnItemClickListener {

		@Override
		public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
			// TODO Auto-generated method stub
			DeviceIdentity deviceId = deviceIdentityList.get(position);
			Device device = upnpService.getRegistry().getDevice(deviceId.getUdn(), true);
			Service dataExchange = device.findService(new UDAServiceId("TrekkerDataExchange"));
			if(dataExchange != null)
				executeAction(upnpService, dataExchange);
		}
		
	}


	
	
	
	
	//***************************************************************************//
	//***************************** Utility methods *****************************//
	//***************************************************************************//
	
	
	protected void searchNetwork() {
		if (upnpService == null) return;
		Toast.makeText(this, "Searching lan", Toast.LENGTH_SHORT).show();
		upnpService.getRegistry().removeAllRemoteDevices();
		upnpService.getControlPoint().search();
	}
	
	
	protected void executeAction(AndroidUpnpService upnpService, Service dataExchangeService) {

		ActionInvocation getDataInvocation = new GetDataActionInvocation(dataExchangeService);

		// Executes asynchronous in the background
		upnpService.getControlPoint().execute(
				new ActionCallback(getDataInvocation) {

					@Override
					public void success(ActionInvocation invocation) {
//						assert invocation.getOutput().length == 0;
						Log.d(TAG, "Successfully called action!");
						
						ActionArgumentValue output = invocation.getOutput("DataList");
						
						assert output != null;	// run this program with -ea (or -enableassertions argument)
						
						Log.d(TAG, "Data type: " + output.getDatatype());
						Log.d(TAG, "Value: " + output.getValue());
						
						Gson gson = new Gson();
						PoiRealTimeInfo data = gson.fromJson((String) output.getValue(), PoiRealTimeInfo.class);
						
						Log.d(TAG, "Data: " + data);
					}

					@Override
					public void failure(ActionInvocation invocation,
							UpnpResponse operation,
							String defaultMsg) {
						Log.d(TAG, defaultMsg);
					}
				}
		);
	}
	

	protected void showToast(final String msg, final boolean longLength) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				Toast.makeText(
						UPnPActivity.this,
						msg,
						longLength ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT
						).show();
			}
		});
	}


	static final Comparator<DeviceDisplay> DISPLAY_COMPARATOR =
			new Comparator<DeviceDisplay>() {
		@Override
		public int compare(DeviceDisplay a, DeviceDisplay b) {
			return a.toString().compareTo(b.toString());
		}
	};
}